package net.luohuasheng.bee.proxy.mybatis.executor.intercetor;


import net.luohuasheng.bee.jdbc.tools.JdbcBuilder;
import net.luohuasheng.bee.jdbc.tools.common.utils.ReflectionUtils;
import net.luohuasheng.bee.jdbc.tools.common.utils.execute.DataUtils;
import net.luohuasheng.bee.jdbc.tools.common.utils.execute.ParameterUtils;
import net.luohuasheng.bee.proxy.core.utils.CastUtils;
import net.luohuasheng.bee.proxy.mybatis.common.utils.ClassUtils;
import net.luohuasheng.bee.proxy.mybatis.common.utils.CrudUtils;
import net.luohuasheng.bee.proxy.mybatis.common.utils.MapperUtils;
import net.luohuasheng.bee.proxy.mybatis.executor.page.Page;
import net.luohuasheng.bee.proxy.mybatis.executor.page.Pageable;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import static net.luohuasheng.bee.proxy.mybatis.executor.intercetor.BaseInterceptor.Index.*;


/**
 * @author luohuasheng
 */
@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
        )})
public class PageInterceptor implements BaseInterceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[MAPPED_STATEMENT_INDEX.getAnInt()];
        Object parameter = args[PARAMETER_INDEX.getAnInt()];
        Pageable pageable = ClassUtils.findObjectFromParams(parameter, Pageable.class);
        if (pageable != null) {
            BoundSql boundSql = ms.getBoundSql(parameter);
            String sql = boundSql.getSql();
            Page<?> page = new Page<>(pageable.isCounting() ? getTotal(invocation, ms, sql, parameter, args, boundSql) : -1, getContent(invocation, ms, pageable, sql, parameter, args, boundSql), pageable);
            List<Page<?>> result = new ArrayList<>(1);
            result.add(page);
            return result;
        }
        callLog(ms);
        return invocation.proceed();
    }

    private List<?> getContent(Invocation invocation, MappedStatement ms, Pageable pageable, String sql, Object parameter, Object[] args, BoundSql boundSql) throws Throwable {
        callLog(ms);
        DataSource dataSource = CrudUtils.dataSource(invocation);
        sql = DataUtils.buildPage(sql, JdbcBuilder.create().getDriverType(dataSource), pageable.getRow(), (pageable.getPage() - 1) * pageable.getRow());
        SqlSource sqlSource = LANGUAGE_DRIVER.createSqlSource(ms.getConfiguration(), "<script>\n\t" + sql + "</script>", null);
        BoundSql newBoundSql = sqlSource.getBoundSql(parameter);
        newBoundSql.getParameterMappings().addAll(0, boundSql.getParameterMappings());
        args[MAPPED_STATEMENT_INDEX.getAnInt()] = MapperUtils.copyFromNewSql(ms, newBoundSql);
        if (args.length == SIX_INDEX.getAnInt()) {
            args[BOUND_SQL_INDEX.getAnInt()] = newBoundSql;
        }
        return CastUtils.cast(invocation.proceed());
    }

    private long getTotal(Invocation invocation, MappedStatement ms, String sql, Object parameter, Object[] args, BoundSql boundSql) throws Throwable {
        callLog(ms);
        SqlSource sqlSource = LANGUAGE_DRIVER.createSqlSource(ms.getConfiguration(), "<script>\n\t" + ParameterUtils.packageCountSql(sql) + "</script>", null);
        BoundSql newBoundSql = sqlSource.getBoundSql(parameter);
        newBoundSql.getParameterMappings().addAll(0, boundSql.getParameterMappings());
        ms = MapperUtils.copyFromNewSql(ms, newBoundSql);
        List<Class<?>> types = new ArrayList<>();
        for (ResultMap resultMap : ms.getResultMaps()) {
            types.add(resultMap.getType());
            ReflectionUtils.setFieldValue(resultMap, "type", Long.class);
        }
        args[MAPPED_STATEMENT_INDEX.getAnInt()] = ms;
        Object key = null;
        if (args.length == SIX_INDEX.getAnInt()) {
            key = args[CACHE_KEY_INDEX.getAnInt()];
            args[CACHE_KEY_INDEX.getAnInt()] = null;
            args[BOUND_SQL_INDEX.getAnInt()] = newBoundSql;
        }
        List<Long> count = CastUtils.cast(invocation.proceed());
        if (args.length == SIX_INDEX.getAnInt()) {
            args[CACHE_KEY_INDEX.getAnInt()] = key;
        }
        List<ResultMap> resultMaps = ms.getResultMaps();
        for (int i = 0; i < resultMaps.size(); i++) {
            ResultMap resultMap = resultMaps.get(i);
            ReflectionUtils.setFieldValue(resultMap, "type", types.get(i));
        }
        return count.get(0);
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 合并属性
    }
}
